1.5 启动并运行起来
前文已经介绍了如何创建控制器、动作和视图,接下来我们要创建一些更具实用价值的东西。
在 Blog 应用中创建一个资源(resource)。资源是一个术语,表示一系列类似对象的集合,如文章、人或动物。资源中的项目可以被创建、读取、更新和删除,这些操作简称 CRUD(Create, Read, Update, Delete)。
Rails 提供了 resources 方法,用于声明标准的 REST 资源。把 article 资源添加到 config/routes.rb 文件,此时文件内容应该变成下面这样:
Rails.application.routes.draw do
resources :articles
root 'welcome#index'
end
执行 bin/rails routes 命令,可以看到所有标准 REST 动作都具有对应的路由。输出结果中各列的意义稍后会作说明,现在只需注意 Rails 从 article 的单数形式推导出了它的复数形式,并进行了合理使用。
$ bin/rails routes
Prefix Verb URI Pattern Controller#Action
articles GET /articles(.:format) articles#index
POST /articles(.:format) articles#create
new_article GET /articles/new(.:format) articles#new
edit_article GET /articles/:id/edit(.:format) articles#edit
article GET /articles/:id(.:format) articles#show
PATCH /articles/:id(.:format) articles#update
PUT /articles/:id(.:format) articles#update
DELETE /articles/:id(.:format) articles#destroy
root GET / welcome#index
下一节,我们将为应用添加新建文章和查看文章的功能。这两个操作分别对应于 CRUD 的“C”和“R”:创建和读取。下面是用于新建文章的表单:

图 1-2:用于新建文章的表单
表单看起来很简陋,不过没关系,之后我们再来美化。
1.5.1 打地基
首先,应用需要一个页面用于新建文章,/articles/new 是个不错的选择。相关路由之前已经定义过了,可以直接访问。打开 http://localhost:3000/articles/new,会看到下面的路由错误:
产生错误的原因是,用于处理该请求的控制器还没有定义。解决问题的方法很简单:创建 Articles 控制器。执行下面的命令:
$ bin/rails generate controller Articles
打开刚刚生成的 app/controllers/articles_controller.rb 文件,会看到一个空的控制器:
class ArticlesController < ApplicationController
end
控制器实际上只是一个继承自 ApplicationController 的类。接在来要在这个类中定义的方法也就是控制器的动作。这些动作对文章执行 CRUD 操作。
注意
在 Ruby 中,有 public、private 和 protected 三种方法,其中只有 public 方法才能作为控制器的动作。详情请参阅 Programming Ruby 一书。
现在刷新 http://localhost:3000/articles/new,会看到一个新错误:

图 1-4:未知动作,在 ArticlesController 中找不到 new 动作
这个错误的意思是,Rails 在刚刚生成的 ArticlesController 中找不到 new 动作。这是因为在 Rails 中生成控制器时,如果不指定想要的动作,生成的控制器就会是空的。
在控制器中手动定义动作,只需要定义一个新方法。打开 app/controllers/articles_controller.rb 文件,在 ArticlesController 类中定义 new 方法,此时控制器应该变成下面这样:
class ArticlesController < ApplicationController
def new
end
end
在 ArticlesController 中定义 new 方法后,再次刷新 http://localhost:3000/articles/new,会看到另一个错误:

图 1-5:未知格式,缺少对应模板
产生错误的原因是,Rails 要求这样的常规动作有用于显示数据的对应视图。如果没有视图可用,Rails 就会抛出异常。
上图中下面的几行都被截断了,下面是完整信息:
ArticlesController#new is missing a template for this request format and variant. request.formats: ["text/html"] request.variant: [] NOTE! For XHR/Ajax or API requests, this action would normally respond with 204 No Content: an empty white screen. Since you’re loading it in a web browser, we assume that you expected to actually render a template, not… nothing, so we’re showing an error to be extra-clear. If you expect 204 No Content, carry on. That’s what you’ll get from an XHR or API request. Give it a shot.
内容还真不少!让我们快速浏览一下,看看各部分是什么意思。
第一部分说明缺少哪个模板,这里缺少的是 articles/new 模板。Rails 首先查找这个模板,如果找不到再查找 application/new 模板。之所以会查找后面这个模板,是因为 ArticlesController 继承自 ApplicationController。
下一部分是 request.formats,说明响应使用的模板格式。当我们在浏览器中请求页面时,request.formats 的值是 text/html,因此 Rails 会查找 HTML 模板。request.variants 指明伺服的是何种物理设备,帮助 Rails 判断该使用哪个模板渲染响应。它的值是空的,因为没有为其提供信息。
在本例中,能够工作的最简单的模板位于 app/views/articles/new.html.erb 文件中。文件的扩展名很重要:第一个扩展名是模板格式,第二个扩展名是模板处理器。Rails 会尝试在 app/views 文件夹中查找 articles/new 模板。这个模板的格式只能是 html,模板处理器只能是 erb、builder 和 coffee 中的一个。:erb 是最常用的 HTML 模板处理器,:builder 是 XML 模板处理器,:coffee 模板处理器用 CoffeeScript 创建 JavaScript 模板。因为我们要创建 HTML 表单,所以应该使用能够在 HTML 中嵌入 Ruby 的 ERB 语言。
所以我们需要创建 articles/new.html.erb 文件,并把它放在应用的 app/views 文件夹中。
现在让我们继续前进。新建 app/views/articles/new.html.erb 文件,添加下面的代码:
刷新 http://localhost:3000/articles/new,会看到页面有了标题。现在路由、控制器、动作和视图都可以协调地工作了!是时候创建用于新建文章的表单了。
1.5.3 创建文章
要消除“未知动作”错误,我们需要修改 app/controllers/articles_controller.rb 文件,在 ArticlesController 类的 new 动作之后添加 create 动作,就像下面这样:
class ArticlesController < ApplicationController
def new
end
def create
end
end
现在重新提交表单,会看到什么都没有改变。别着急!这是因为当我们没有说明动作的响应是什么时,Rails 默认返回 204 No Content response。我们刚刚添加了 create 动作,但没有说明响应是什么。这里,create 动作应该把新建文章保存到数据库中。
表单提交后,其字段以参数形式传递给 Rails,然后就可以在控制器动作中引用这些参数,以执行特定任务。要想查看这些参数的内容,可以把 create 动作的代码修改成下面这样:
def create
render plain: params[:article].inspect
end
这里 render 方法接受了一个简单的散列(hash)作为参数,:plain 键的值是 params[:article].inspect。params 方法是代表表单提交的参数(或字段)的对象。params 方法返回 ActionController::Parameters 对象,这个对象允许使用字符串或符号访问散列的键。这里我们只关注通过表单提交的参数。
提示
请确保牢固掌握 params 方法,这个方法很常用。让我们看一个示例 URL:http://www.example.com/?username=dhh&email=dhh@email.com。在这个 URL 中,params[:username] 的值是“dhh”,params[:email] 的值是“dhh@email.com”。
如果再次提交表单,就不会再看到缺少模板错误,而是会看到下面这些内容:
<ActionController::Parameters {"title"=>"First Article!", "text"=>"This is my first article."} permitted: false>create 动作把表单提交的参数都显示出来了,但这并没有什么用,只是看到了参数实际上却什么也没做。
1.5.4 创建 Article 模型
在 Rails 中,模型使用单数名称,对应的数据库表使用复数名称。Rails 提供了用于创建模型的生成器,大多数 Rails 开发者在新建模型时倾向于使用这个生成器。要想新建模型,请执行下面的命令:
$ bin/rails generate model Article title:string text:text
上面的命令告诉 Rails 创建 Article 模型,并使模型具有字符串类型的 title 属性和文本类型的 text 属性。这两个属性会自动添加到数据库的 articles 表中,并映射到 Article 模型上。
为此 Rails 会创建一堆文件。这里我们只关注 app/models/article.rb 和 db/migrate/20140120191729_create_articles.rb 这两个文件 (后面这个文件名和你看到的可能会有点不一样)。后者负责创建数据库结构,下一节会详细说明。
提示
Active Record 很智能,能自动把数据表的字段名映射到模型属性上,因此无需在 Rails 模型中声明属性,让 Active Record 自动完成即可。
1.5.5 运行迁移
如前文所述,bin/rails generate model 命令会在 db/migrate 文件夹中生成数据库迁移文件。迁移是用于简化创建和修改数据库表操作的 Ruby 类。Rails 使用 rake 命令运行迁移,并且在迁移作用于数据库之后还可以撤销迁移操作。迁移的文件名包含了时间戳,以确保迁移按照创建时间顺序运行。
让我们看一下 db/migrate/YYYYMMDDHHMMSS_create_articles.rb 文件(记住,你的文件名可能会有点不一样),会看到下面的内容:
class CreateArticles < ActiveRecord::Migration[5.0]
def change
create_table :articles do |t|
t.string :title
t.text :text
t.timestamps
end
end
end
上面的迁移创建了 change 方法,在运行迁移时会调用这个方法。在 change 方法中定义的操作都是可逆的,在需要时 Rails 知道如何撤销这些操作。运行迁移后会创建 articles 表,这个表包括一个字符串字段和一个文本字段,以及两个用于跟踪文章创建和更新时间的时间戳字段。
现在可以使用 bin/rails 命令运行迁移了:
Rails 会执行迁移命令并告诉我们它创建了 Articles 表。
== CreateArticles: migrating ==================================================
-- create_table(:articles)
-> 0.0019s
== CreateArticles: migrated (0.0020s) =========================================
注意
因为默认情况下我们是在开发环境中工作,所以上述命令应用于 config/database.yml 文件中 development 部分定义的的数据库。要想在其他环境中执行迁移,例如生产环境,就必须在调用命令时显式传递环境变量:bin/rails db:migrate RAILS_ENV=production。
1.5.6 在控制器中保存数据
回到 ArticlesController,修改 create 动作,使用新建的 Article 模型把数据保存到数据库。打开 app/controllers/articles_controller.rb 文件,像下面这样修改 create 动作:
def create
@article = Article.new(params[:article])
@article.save
redirect_to @article
end
让我们看一下上面的代码都做了什么:Rails 模型可以用相应的属性初始化,它们会自动映射到对应的数据库字段。create 动作中的第一行代码完成的就是这个操作(记住,params[:article] 包含了我们想要的属性)。接下来 @article.save 负责把模型保存到数据库。最后把页面重定向到 show 动作,这个 show 动作我们稍后再定义。
提示
你可能想知道,为什么在上面的代码中 Article.new 的 A 是大写的,而在本文的其他地方引用 articles 时大都是小写的。因为这里我们引用的是在 app/models/article.rb 文件中定义的 Article 类,而在 Ruby 中类名必须以大写字母开头。
提示
之后我们会看到,@article.save 返回布尔值,以表明文章是否保存成功。
现在访问 http://localhost:3000/articles/new,我们就快要能够创建文章了,但我们还会看到下面的错误:

图 1-7:禁用属性错误
Rails 提供了多种安全特性来帮助我们编写安全的应用,上面看到的就是一种安全特性。这个安全特性叫做 健壮参数(strong parameter),要求我们明确地告诉 Rails 哪些参数允许在控制器动作中使用。
为什么我们要这样自找麻烦呢?一次性获取所有控制器参数并自动赋值给模型显然更简单,但这样做会造成恶意使用的风险。设想一下,如果有人对服务器发起了一个精心设计的请求,看起来就像提交了一篇新文章,但同时包含了能够破坏应用完整性的额外字段和值,会怎么样?这些恶意数据会批量赋值给模型,然后和正常数据一起进入数据库,这样就有可能破坏我们的应用或者造成更大损失。
所以我们只能为控制器参数设置白名单,以避免错误地批量赋值。这里,我们想在 create 动作中合法使用 title 和 text 参数,为此需要使用 require 和 permit 方法。像下面这样修改 create 动作中的一行代码:
@article = Article.new(params.require(:article).permit(:title, :text))
上述代码通常被抽象为控制器类的一个方法,以便在控制器的多个动作中重用,例如在 create 和 update 动作中都会用到。除了批量赋值问题,为了禁止从外部调用这个方法,通常还要把它设置为 private。最后的代码像下面这样:
def create
@article = Article.new(article_params)
@article.save
redirect_to @article
end
private
def article_params
params.require(:article).permit(:title, :text)
end
提示
关于键壮参数的更多介绍,请参阅上面提供的参考资料和这篇博客。
1.5.7 显示文章
现在再次提交表单,Rails 会提示找不到 show 动作。尽管这个提示没有多大用处,但在继续前进之前我们还是先添加 show 动作吧。
之前我们在 bin/rails routes 命令的输出结果中看到,show 动作对应的路由是:
article GET /articles/:id(.:format) articles#show
特殊句法 :id 告诉 Rails 这个路由期望接受 :id 参数,在这里也就是文章的 ID。
和前面一样,我们需要在 app/controllers/articles_controller.rb 文件中添加 show 动作,并创建对应的视图文件。
注意
常见的做法是按照以下顺序在控制器中放置标准的 CRUD 动作:index,show,new,edit,create,update 和 destroy。你也可以按照自己的顺序放置这些动作,但要记住它们都是公开方法,如前文所述,必须放在控制器的私有方法或受保护的方法之前才能正常工作。
有鉴于此,让我们像下面这样添加 show 动作:
class ArticlesController < ApplicationController
def show
@article = Article.find(params[:id])
end
def new
end
# 为了行文简洁,省略以下内容
上面的代码中有几个问题需要注意。我们使用 Article.find 来查找文章,并传入 params[:id] 以便从请求中获得 :id 参数。我们还使用实例变量(前缀为 @)保存对文章对象的引用。这样做是因为 Rails 会把所有实例变量传递给视图。
现在新建 app/views/articles/show.html.erb 文件,添加下面的代码:
<p>
<strong>Title:</strong>
<%= @article.title %>
</p>
<p>
<strong>Text:</strong>
<%= @article.text %>
</p>
通过上面的修改,我们终于能够新建文章了。访问 http://localhost:3000/articles/new,自己试一试吧!

图 1-8:显示文章
1.5.8 列出所有文章
我们还需要列出所有文章,下面就来完成这个功能。在 bin/rails routes 命令的输出结果中,和列出文章对应的路由是:
articles GET /articles(.:format) articles#index
在 app/controllers/articles_controller.rb 文件的 ArticlesController 中为上述路由添加对应的 index 动作。在编写 index 动作时,常见的做法是把它作为控制器的第一个方法,就像下面这样:
class ArticlesController < ApplicationController
def index
@articles = Article.all
end
def show
@article = Article.find(params[:id])
end
def new
end
# 为了行文简洁,省略以下内容
最后,在 app/views/articles/index.html.erb 文件中为 index 动作添加视图:
<h1>Listing articles</h1>
<table>
<tr>
<th>Title</th>
<th>Text</th>
</tr>
<% @articles.each do |article| %>
<tr>
<td><%= article.title %></td>
<td><%= article.text %></td>
<td><%= link_to 'Show', article_path(article) %></td>
</tr>
<% end %>
</table>
现在访问 http://localhost:3000/articles,会看到已创建的所有文章的列表。
1.5.9 添加链接
至此,我们可以创建、显示、列出文章了。下面我们添加一些指向这些页面的链接。
打开 app/views/welcome/index.html.erb 文件,修改成下面这样:
<h1>Hello, Rails!</h1>
<%= link_to 'My Blog', controller: 'articles' %>
link_to 方法是 Rails 内置的视图辅助方法之一,用于创建基于链接文本和地址的超链接。在这里地址指的是文章列表页面的路径。
接下来添加指向其他视图的链接。首先在 app/views/articles/index.html.erb 文件中添加“New Article”链接,把这个链接放在 <table> 标签之前:
<%= link_to 'New article', new_article_path %>
点击这个链接会打开用于新建文章的表单。
接下来在 app/views/articles/new.html.erb 文件中添加返回 index 动作的链接,把这个链接放在表单之后:
<%= form_for :article, url: articles_path do |f| %>
...
<% end %>
<%= link_to 'Back', articles_path %>
最后,在 app/views/articles/show.html.erb 模板中添加返回 index 动作的链接,这样用户看完一篇文章后就可以返回文章列表页面了:
<p>
<strong>Title:</strong>
<%= @article.title %>
</p>
<p>
<strong>Text:</strong>
<%= @article.text %>
</p>
<%= link_to 'Back', articles_path %>
提示
链接到当前控制器的动作时不需要指定 :controller 选项,因为 Rails 默认使用当前控制器。
提示
在开发环境中(默认情况下我们是在开发环境中工作),Rails 针对每个浏览器请求都会重新加载应用,因此对应用进行修改之后不需要重启服务器。
1.5.10 添加验证
app/models/article.rb 模型文件简单到只有两行代码:
class Article < ApplicationRecord
end
虽然这个文件中代码很少,但请注意 Article 类继承自 ApplicationRecord 类,而 ApplicationRecord 类继承自 ActiveRecord::Base 类。正是 ActiveRecord::Base 类为 Rails 模型提供了大量功能,包括基本的数据库 CRUD 操作(创建、读取、更新、删除)、数据验证,以及对复杂搜索的支持和关联多个模型的能力。
Rails 提供了许多方法用于验证传入模型的数据。打开 app/models/article.rb 文件,像下面这样修改:
class Article < ApplicationRecord
validates :title, presence: true,
length: { minimum: 5 }
end
添加的代码用于确保每篇文章都有标题,并且标题长度不少于 5 个字符。在 Rails 模型中可以验证多种条件,包括字段是否存在、字段是否唯一、字段的格式、关联对象是否存在,等等。关于验证的更多介绍,请参阅第 4 章。
现在验证已经添加完毕,如果我们在调用 @article.save 时传递了无效的文章数据,验证就会返回 false。再次打开 app/controllers/articles_controller.rb 文件,会看到我们并没有在 create 动作中检查 @article.save 的调用结果。在这里如果 @article.save 失败了,就需要把表单再次显示给用户。为此,需要像下面这样修改 app/controllers/articles_controller.rb 文件中的 new 和 create 动作:
def new
@article = Article.new
end
def create
@article = Article.new(article_params)
if @article.save
redirect_to @article
else
render 'new'
end
end
private
def article_params
params.require(:article).permit(:title, :text)
end
在上面的代码中,我们在 new 动作中创建了新的实例变量 @article,稍后你就会知道为什么要这样做。
注意在 create 动作中,当 save 返回 false 时,我们用 render 代替了 redirect_to。使用 render 方法是为了把 @article 对象回传给 new 模板。这里渲染操作是在提交表单的这个请求中完成的,而 redirect_to 会告诉浏览器发起另一个请求。
刷新 http://localhost:3000/articles/new,试着提交一篇没有标题的文章,Rails 会返回这个表单,但这种处理方式没有多大用处,更好的做法是告诉用户哪里出错了。为此需要修改 app/views/articles/new.html.erb 文件,添加显示错误信息的代码:
<%= form_for :article, url: articles_path do |f| %>
<% if @article.errors.any? %>
<div id="error_explanation">
<h2>
<%= pluralize(@article.errors.count, "error") %> prohibited
this article from being saved:
</h2>
<ul>
<% @article.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<p>
<%= f.label :title %><br>
<%= f.text_field :title %>
</p>
<p>
<%= f.label :text %><br>
<%= f.text_area :text %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
<%= link_to 'Back', articles_path %>
上面我们添加了一些代码。我们使用 @article.errors.any? 检查是否有错误,如果有错误就使用 @article.errors.full_messages 列出所有错误信息。
pluralize 是 Rails 提供的辅助方法,接受一个数字和一个字符串作为参数。如果数字比 1 大,字符串会被自动转换为复数形式。
在 ArticlesController 中添加 @article = Article.new 是因为如果不这样做,在视图中 @article 的值就会是 nil,这样在调用 @article.errors.any? 时就会抛出错误。
提示
Rails 会自动用 div 包围含有错误信息的字段,并为这些 div 添加 field_with_errors 类。我们可以定义 CSS 规则突出显示错误信息。
当我们再次访问 http://localhost:3000/articles/new,试着提交一篇没有标题的文章,就会看到友好的错误信息。
1.5.11 更新文章
我们已经介绍了 CRUD 操作中的“CR”两种操作,下面让我们看一下“U”操作,也就是更新文章。
第一步要在 ArticlesController 中添加 edit 动作,通常把这个动作放在 new 动作和 create 动作之间,就像下面这样:
def new
@article = Article.new
end
def edit
@article = Article.find(params[:id])
end
def create
@article = Article.new(article_params)
if @article.save
redirect_to @article
else
render 'new'
end
end
接下来在视图中添加一个表单,这个表单类似于前文用于新建文章的表单。创建 app/views/articles/edit.html.erb 文件,添加下面的代码:
<h1>Editing article</h1>
<%= form_for :article, url: article_path(@article), method: :patch do |f| %>
<% if @article.errors.any? %>
<div id="error_explanation">
<h2>
<%= pluralize(@article.errors.count, "error") %> prohibited
this article from being saved:
</h2>
<ul>
<% @article.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<p>
<%= f.label :title %><br>
<%= f.text_field :title %>
</p>
<p>
<%= f.label :text %><br>
<%= f.text_area :text %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
<%= link_to 'Back', articles_path %>
上面的代码把表单指向了 update 动作,这个动作稍后我们再来定义。
method: :patch 选项告诉 Rails 使用 PATCH 方法提交表单。根据 REST 协议,PATCH 方法是更新资源时使用的 HTTP 方法。
form_for 辅助方法的第一个参数可以是对象,例如 @article,form_for 辅助方法会用这个对象的字段来填充表单。如果传入和实例变量(@article)同名的符号(:article),也会自动产生相同效果,上面的代码使用的就是符号。关于 form_for 辅助方法参数的更多介绍,请参阅 form_for 的文档。
接下来在 app/controllers/articles_controller.rb 文件中创建 update 动作,把这个动作放在 create 动作和 private 方法之间:
def create
@article = Article.new(article_params)
if @article.save
redirect_to @article
else
render 'new'
end
end
def update
@article = Article.find(params[:id])
if @article.update(article_params)
redirect_to @article
else
render 'edit'
end
end
private
def article_params
params.require(:article).permit(:title, :text)
end
update 动作用于更新已有记录,它接受一个散列作为参数,散列中包含想要更新的属性。和之前一样,如果更新文章时发生错误,就需要把表单再次显示给用户。
上面的代码重用了之前为 create 动作定义的 article_params 方法。
提示
不用把所有属性都传递给 update 方法。例如,调用 @article.update(title: 'A new title') 时,Rails 只更新 title 属性而不修改其他属性。
最后,我们想在文章列表中显示指向 edit 动作的链接。打开 app/views/articles/index.html.erb 文件,在 Show 链接后面添加 Edit 链接:
<table>
<tr>
<th>Title</th>
<th>Text</th>
<th colspan="2"></th>
</tr>
<% @articles.each do |article| %>
<tr>
<td><%= article.title %></td>
<td><%= article.text %></td>
<td><%= link_to 'Show', article_path(article) %></td>
<td><%= link_to 'Edit', edit_article_path(article) %></td>
</tr>
<% end %>
</table>
接着在 app/views/articles/show.html.erb 模板中添加 Edit 链接,这样文章页面也有 Edit 链接了。把这个链接添加到模板底部:
...
<%= link_to 'Edit', edit_article_path(@article) %> |
<%= link_to 'Back', articles_path %>
下面是文章列表现在的样子:

图 1-10:文章列表
1.5.12 使用局部视图去掉视图中的重复代码
编辑文章页面和新建文章页面看起来很相似,实际上这两个页面用于显示表单的代码是相同的。现在我们要用局部视图来去掉这些重复代码。按照约定,局部视图的文件名以下划线开头。
新建 app/views/articles/_form.html.erb 文件,添加下面的代码:
<%= form_for @article do |f| %>
<% if @article.errors.any? %>
<div id="error_explanation">
<h2>
<%= pluralize(@article.errors.count, "error") %> prohibited
this article from being saved:
</h2>
<ul>
<% @article.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<p>
<%= f.label :title %><br>
<%= f.text_field :title %>
</p>
<p>
<%= f.label :text %><br>
<%= f.text_area :text %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
除了第一行 form_for 的用法变了之外,其他代码都和之前一样。之所以能用这个更短、更简单的 form_for 声明来代替新建文章页面和编辑文章页面的两个表单,是因为 @article 是一个资源,对应于一套 REST 式路由,Rails 能够推断出应该使用哪个地址和方法。关于 form_for 用法的更多介绍,请参阅“面向资源的风格”。
现在更新 app/views/articles/new.html.erb 视图,以使用新建的局部视图。把文件内容替换为下面的代码:
<h1>New article</h1>
<%= render 'form' %>
<%= link_to 'Back', articles_path %>
然后按照同样的方法修改 app/views/articles/edit.html.erb 视图:
<h1>Edit article</h1>
<%= render 'form' %>
<%= link_to 'Back', articles_path %>
1.5.13 删除文章
现在该介绍 CRUD 中的“D”操作了,也就是从数据库删除文章。按照 REST 架构的约定,在 bin/rails routes 命令的输出结果中删除文章的路由是:
DELETE /articles/:id(.:format) articles#destroy
删除资源的路由应该使用 delete 路由方法。如果在删除资源时仍然使用 get 路由,就可能给那些设计恶意地址的人提供可乘之机:
<a href='http://example.com/articles/1/destroy'>look at this cat!</a>
我们用 delete 方法来删除资源,对应的路由会映射到 app/controllers/articles_controller.rb 文件中的 destroy 动作,稍后我们要创建这个动作。destroy 动作是控制器中的最后一个 CRUD 动作,和其他公共 CRUD 动作一样,这个动作应该放在 private 或 protected 方法之前。打开 app/controllers/articles_controller.rb 文件,添加下面的代码:
def destroy
@article = Article.find(params[:id])
@article.destroy
redirect_to articles_path
end
在 app/controllers/articles_controller.rb 文件中,ArticlesController 的完整代码应该像下面这样:
class ArticlesController < ApplicationController
def index
@articles = Article.all
end
def show
@article = Article.find(params[:id])
end
def new
@article = Article.new
end
def edit
@article = Article.find(params[:id])
end
def create
@article = Article.new(article_params)
if @article.save
redirect_to @article
else
render 'new'
end
end
def update
@article = Article.find(params[:id])
if @article.update(article_params)
redirect_to @article
else
render 'edit'
end
end
def destroy
@article = Article.find(params[:id])
@article.destroy
redirect_to articles_path
end
private
def article_params
params.require(:article).permit(:title, :text)
end
end
在 Active Record 对象上调用 destroy 方法,就可从数据库中删除它们。注意,我们不需要为 destroy 动作添加视图,因为完成操作后它会重定向到 index 动作。
最后,在 index 动作的模板(app/views/articles/index.html.erb)中加上“Destroy”链接,这样就大功告成了:
<h1>Listing Articles</h1>
<%= link_to 'New article', new_article_path %>
<table>
<tr>
<th>Title</th>
<th>Text</th>
<th colspan="3"></th>
</tr>
<% @articles.each do |article| %>
<tr>
<td><%= article.title %></td>
<td><%= article.text %></td>
<td><%= link_to 'Show', article_path(article) %></td>
<td><%= link_to 'Edit', edit_article_path(article) %></td>
<td><%= link_to 'Destroy', article_path(article),
method: :delete,
data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</table>
在上面的代码中,link_to 辅助方法生成“Destroy”链接的用法有点不同,其中第二个参数是具名路由(named route),还有一些选项作为其他参数。method: :delete 和 data: { confirm: 'Are you sure?' } 选项用于设置链接的 HTML5 属性,这样点击链接后 Rails 会先向用户显示一个确认对话框,然后用 delete 方法发起请求。这些操作是通过 JavaScript 脚本 jquery_ujs 实现的,这个脚本在生成应用骨架时已经被自动包含在了应用的布局中(app/views/layouts/application.html.erb)。如果没有这个脚本,确认对话框就无法显示。
提示
关于 jQuery 非侵入式适配器(jQuery UJS)的更多介绍,请参阅第 24 章。
恭喜你!现在你已经可以创建、显示、列出、更新和删除文章了!
提示
通常 Rails 鼓励用资源对象来代替手动声明路由。关于路由的更多介绍,请参阅第 13 章。